<template>
  <!-- 设计稿 1920×1080，根据窗口状态动态切换模式 :fullScreen="true" -->
  <v-scale-screen width="1920" height="1080"  :boxStyle="{ background: '#131615' }">
    <div class="container">
      <!-- 左上角时间显示 -->
      <TimeDisplay />

    <div class="main-content">
      <!-- 左侧按钮栏 -->
      <div class="sidebar-buttons">
        <button @click="createNewSession" class="sidebar-btn" title="新建会话">
          <img :src="newSessionIcon" class="btn-aicon" alt="新建会话" />
        </button>
        <button @click="showHistory" class="sidebar-btn" title="历史记录">
          <img :src="historyIcon" class="btn-aicon" alt="历史记录" />
        </button>
        <button @click="toggleMute" class="sidebar-btn mute-btn" :class="{ 'muted': isMuted }"
          :title="isMuted ? '静音' : '播放'">
          <img :src="isMuted ? muteIcon : playIcon" class="btn-aicon" :alt="isMuted ? '静音' : '播放'" />
        </button>
      </div>

      <!-- 左侧：消息显示区域 -->
      <div class="message-display">
        <!-- 默认显示图片 -->
        <!-- <div class="image-container">
          <img :src="manImage" class="man-image" alt="角色" />
        </div> -->

        <!-- 视频播放器 -->
        <div class="video-container">
          <div class="video-name">{{ videoFileName }}</div>
          <video 
            ref="videoPlayer"
            :src="videoSrc" 
            :loop="isLooping"
            autoplay 
            muted 
            class="video-player"
            @ended="onVideoEnded"
          ></video>
        </div>
      </div>

      <!-- 右侧：对话区域 -->
      <div class="chat-section">
        <!-- 顶部标题 -->
        <div class="chat-header">
          <h3>思考链路</h3>
        </div>

        <!-- 聊天消息显示区域 -->
        <div class="chat-messages" ref="chatMessagesContainer">
          <!-- Loading 遮罩层 -->
          <div v-if="isLoadingMessages" class="loading-overlay">
            <img :src="loadingSpinner" alt="加载中" class="loading-spinner-img" />
          </div>
          <div class="message-list">
            <!-- 默认AI欢迎消息（始终显示） -->
            <div class="message-item ai-message">
              <div class="message-avatar">
                <img :src="aiAvatar" alt="AI" class="avatar-img" />
              </div>
              <div class="message-content">
                <div class="message-bubble">
                  您好，我是您的智能助理"XX"，您可以对我下发指令。
                </div>
              </div>
            </div>

            <!-- 聊天记录 -->
            <div v-for="(msg, index) in chatMessages" :key="index" class="message-item"
              :class="{ 'user-message': msg.type === 'user', 'ai-message': msg.type === 'ai', 'thinking-message': msg.type === 'thinking' }">
              <!-- AI消息头像 -->
              <div v-if="msg.type === 'ai' || msg.type === 'thinking'" class="message-avatar">
                <img :src="aiAvatar" alt="AI" class="avatar-img" />
              </div>
              <div class="message-content">
                <!-- 思考框 - AI思考状态 -->
                <div v-if="msg.type === 'thinking'" class="thinking-container">
                  <!-- 思考状态标题 -->
                  <div class="thinking-header" @click="toggleThinkingCollapse(index)">
                    <div class="thinking-left">
                      <img :src="starIcon" alt="思考" class="thinking-icon" />
                      <span class="thinking-text">{{ msg.status === 'thinking' ? '思考中...' : '思考完成' }}</span>
                      <img :src="msg.collapsed ? fangIcon : shouIcon" alt="收缩" class="collapse-icon" />
                    </div>
                  </div>

                  <!-- 思考内容包裹层，用于整体收缩 -->
                  <div class="thinking-content-wrapper" v-show="!msg.collapsed">
                    <!-- 多个意图理解区域（每个智能体一个） -->
                    <div v-for="(section, sectionIndex) in msg.sections" :key="sectionIndex" class="intent-section">
                      <div class="intent-header">
                        <div class="intent-left">
                          <span class="intent-title">{{ section.agentName }}</span>
                        </div>
                      </div>
                      <div class="intent-content" :ref="el => setIntentContentRef(el, index, sectionIndex)">
                        <!-- 智能体内容（将 markdown 转换为 HTML 后渲染） -->
                        <div class="agent-content" v-html="marked(section.content) + ((isAnswering && index === chatMessages.length - 1 && sectionIndex === msg.sections.length - 1) ? '<span class=\'loading-indicator\'></span>' : '')"></div>
                      </div>
                    </div>
                  </div>
                </div>
                <!-- 普通消息气泡 -->
                <div v-else class="message-bubble" 
                  :class="{ 'summary-bubble': msg.isSummary }"
                  :style="msg.isSummary ? { backgroundImage: `url(${summaryBg})` } : {}">
                  {{ msg.content }}<span v-if="msg.type === 'ai' && (msg.isStreaming || (isAnswering && index === chatMessages.length - 1))" class="loading-indicator"></span>
                </div>
              </div>
              <!-- 用户消息头像 -->
              <div v-if="msg.type === 'user'" class="message-avatar">
                <img :src="userAvatar" alt="User" class="avatar-img" />
              </div>
            </div>
          </div>
        </div>

        <!-- 输入区域 -->
        <div class="chat-input-area">
          <div class="input-container">
            <textarea v-model="message" placeholder="请输入您的消息..." @keydown.enter.exact.prevent="sendMessage"
              class="chat-input"></textarea>
            <button @click="sendMessage" class="send-button" :class="{ 'is-answering': isAnswering }"
              :disabled="isAnswering">
              <img v-if="isAnswering" :src="stopIcon" alt="停止" class="send-icon" />
              <img v-else :src="sendIcon" alt="发送" class="send-icon" />
            </button>
          </div>
        </div>
      </div>
    </div>

    <!-- 历史记录弹框 -->
    <transition name="dialog-fade">
      <div v-if="showHistoryDialog" class="history-dialog-overlay">
        <div class="history-dialog">
          <div class="history-dialog-header">
            <h3>历史记录</h3>
            <button @click="closeHistoryDialog" class="close-btn" title="关闭">✕</button>
          </div>
          <div class="history-dialog-content" @scroll="handleHistoryScroll">
            <div v-for="item in historyList" :key="item.session_id || item.id" class="history-item" @click="selectHistoryItem(item)">
              <img :src="messageIcon" class="history-icon" alt="消息" />
              <div class="history-info">
                <div class="history-name">{{ item.title || item.name }}</div>
                <div class="history-time">{{ formatDateTime(item.updated_at || item.created_at || item.time) }}</div>
              </div>
            </div>
            <!-- 加载更多提示 -->
            <div v-if="isLoadingHistory" class="loading-more">
              加载中...
            </div>
            <div v-else-if="!hasMoreHistory && historyList.length >= 12" class="no-more">
              没有更多了
            </div>
          </div>
        </div>
      </div>
    </transition>
    </div>
  </v-scale-screen>
</template>

<script setup lang="ts">
/**
 * MainConsole 主控制台组件
 * 
 * 核心功能:
 * 1. SSE流式聊天界面 - 接收后端推送的打字机效果数据
 * 2. 思考链路展示 - 显示AI的思考过程(智能体、节点、内容)
 * 3. 历史记录管理 - 加载和展示历史对话
 * 4. 窗口间通信 - 与Machine组件进行点对点消息通信
 * 
 * 技术特点:
 * - 完全由后端控制打字机速度(前端只负责接收和显示)
 * - 动态UI创建(基于SSE元数据创建空容器,再逐字符填充内容)
 * - 自动滚动(消息区域和意图内容区域双重滚动)
 * - 响应式布局(使用v-scale-screen适配1920×1080设计稿)
 */
import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from 'vue'
import {
  getHistoryChatList,
  // createHistoryChat,
  // getHistoryChatById,
  sendSSEChat,
  type HistoryItem,
  type Message
} from '../api'
import { formatDateTime, convertScreen1ToMessages } from '../utils/messageUtils'
import '../style/MainConsole.css'
// 引入 v-scale-screen 组件 - 用于响应式缩放适配
import VScaleScreen from 'v-scale-screen'
// 引入时间显示组件
import TimeDisplay from '../subcomponents/Common/TimeDisplay.vue'
// 引入 marked 库用于 markdown 转 HTML
import { marked } from 'marked'

// ==================== 响应式状态变量 ====================

/** 用户输入的消息内容 */
const message = ref<string>('')
/** 状态提示文本(临时显示,2秒后自动清空) */
const status = ref<string>('')
/** 接收到的消息内容(暂未使用) */
const receivedMessage = ref<string>('')
/** 最后更新时间(暂未使用) */
const lastUpdate = ref<string>('')
/** 静音状态标志 */
const isMuted = ref<boolean>(false)
/** 历史记录弹框显示状态 */
const showHistoryDialog = ref<boolean>(false)
/** AI是否正在回答(用于禁用发送按钮和显示停止图标) */
const isAnswering = ref<boolean>(false)

// ==================== 历史功能相关变量 ====================

/**
 * 方案展示完成标志
 * 用途: 延迟显示某些AI气泡,等待Machine组件的方案展示完成通知
 * 注: 当前架构中此功能可能已不需要,保留以兼容旧逻辑
 */
const solutionDisplayCompleted = ref<boolean>(false)
let pendingPhase2Setup: (() => void) | null = null

/**
 * 全局思考框索引(跨函数访问)
 * 注: 当前已由sendMessage中的currentThinkingBoxIndex替代,保留以兼容
 */
let globalPhase2ThinkingIndex: number | null = null
let globalCurrentThinkingIndex: number | null = null  // 新增：当前活跃的思考框索引

/**
 * 全局实时显示函数引用
 * 用途: 在onMounted的消息监听器中使用,用于显示结论气泡
 * 注: 这些函数在sendMessage中定义,通过闭包保存引用供后续使用
 */
let globalAddToGlobalQueue: ((task: () => Promise<void>) => void) | null = null
let globalCreateAIBubble: ((content: string, isSummary?: boolean, customTime?: string) => Promise<void>) | null = null

// ==================== DOM引用 ====================

/** 聊天消息容器的DOM引用 - 用于滚动到底部 */
const chatMessagesContainer = ref<HTMLElement | null>(null)

/**
 * 意图内容区域的DOM引用集合
 * 
 * 结构: Map<string, HTMLElement>
 * Key格式: `${messageIndex}-${sectionIndex}`
 * 用途: 在打字机效果时滚动对应的意图内容区域到底部
 * 
 * 为什么需要:
 * - 每个思考框可能有多个智能体section
 * - 每个section都有独立的滚动区域
 * - 打字时需要准确定位到当前section并滚动
 */
const intentContentRefs = ref<Map<string, HTMLElement>>(new Map())

// ==================== 静态资源导入 ====================

// 视频源
const greetingVideo = new URL('../assets/Webm/打招呼.webm', import.meta.url).href
const standbyVideo = new URL('../assets/Webm/待机.webm', import.meta.url).href
const wakeupVideo = new URL('../assets/Webm/唤醒.webm', import.meta.url).href
const thinkingVideo = new URL('../assets/Webm/思考中.webm', import.meta.url).href
const thinkingEndVideo = new URL('../assets/Webm/思考结束.webm', import.meta.url).href

/** 当前视频源 */
const videoSrc = ref<string>(greetingVideo)

/** 是否循环播放(待机和思考中视频需要循环) */
const isLooping = ref<boolean>(false)

/** 视频播放器DOM引用 */
const videoPlayer = ref<HTMLVideoElement | null>(null)

/** 视频状态机 */
let videoStateTimer: NodeJS.Timeout | null = null
let isWaitingForUserInput = false  // 是否在等待用户输入

/** 从视频路径中提取文件名 */
const videoFileName = computed(() => {
  const path = videoSrc.value
  const fileName = path.split('/').pop() || ''
  const nameWithoutExt = fileName.replace('.webm', '')
  // 解码URL编码的中文字符
  try {
    return decodeURIComponent(nameWithoutExt)
  } catch (e) {
    return nameWithoutExt
  }
})

/**
 * 清除视频状态定时器
 */
const clearVideoTimer = (): void => {
  if (videoStateTimer) {
    clearTimeout(videoStateTimer)
    videoStateTimer = null
  }
}

/**
 * 切换视频
 * @param video - 视频源
 * @param loop - 是否循环播放
 */
const switchVideo = (video: string, loop: boolean = false): void => {
  // 切换视频前清除所有定时器，防止之前的逻辑干扰（例如唤醒后的倒计时打断思考中）
  clearVideoTimer()
  videoSrc.value = video
  isLooping.value = loop
  console.log(`切换视频: ${video.split('/').pop()}, 循环: ${loop}`)
}

/**
 * 视频播放结束事件处理
 */
const onVideoEnded = (): void => {
  const currentVideo = videoSrc.value
  
  // 打招呼视频结束 -> 待机
  if (currentVideo === greetingVideo) {
    switchVideo(standbyVideo, true)
    console.log('打招呼视频结束,切换到待机')
  }
  // 思考结束视频结束 -> 等待10秒后切换到唤醒
  else if (currentVideo === thinkingEndVideo) {
    clearVideoTimer()
    console.log('思考结束视频结束,10秒后切换到唤醒')
    
    // 等待10秒后切换到唤醒
    videoStateTimer = setTimeout(() => {
      isWaitingForUserInput = true  // 标记为等待用户输入状态
      switchVideo(wakeupVideo, false)
      console.log('10秒已到,切换到唤醒视频')
    }, 10000)
  }
  // 唤醒视频结束 -> 根据状态判断下一步
  else if (currentVideo === wakeupVideo) {
    // 如果正在等待用户输入(思考结束后的唤醒),则等待15秒后切换到待机
    if (isWaitingForUserInput) {
      clearVideoTimer()
      console.log('唤醒视频结束,等待用户输入5秒')
      
      // 等待5秒,如果没有点击发送按钮则切换到待机
      videoStateTimer = setTimeout(() => {
        if (isWaitingForUserInput) {
          switchVideo(standbyVideo, true)
          isWaitingForUserInput = false
          console.log('15秒内未收到用户输入,切换到待机')
        }
      }, 15000)
    } else {
      // 如果是点击发送按钮后的唤醒,等待SSE数据
      console.log('唤醒视频结束,等待SSE数据')
    }
  }
}

// 左侧按钮栏图标
const newSessionIcon = new URL('../assets/MainConsole/icon-new-session.png', import.meta.url).href
const historyIcon = new URL('../assets/MainConsole/icon-history.png', import.meta.url).href
const muteIcon = new URL('../assets/MainConsole/icon-mute.png', import.meta.url).href
const playIcon = new URL('../assets/MainConsole/icon-play.png', import.meta.url).href

// 聊天区域头像图标
const aiAvatar = new URL('../assets/MainConsole/avatar-ai.png', import.meta.url).href
const userAvatar = new URL('../assets/MainConsole/avatar-user.png', import.meta.url).href

// 思考框相关图标
const starIcon = new URL('../assets/MainConsole/icon-thinking-star.png', import.meta.url).href  // 思考状态图标

// 意图理解区域图标
const shouIcon = new URL('../assets/MainConsole/icon-collapse.png', import.meta.url).href  // 收起图标
const fangIcon = new URL('../assets/MainConsole/icon-expand.png', import.meta.url).href  // 展开图标

// 输入区域图标
const sendIcon = new URL('../assets/MainConsole/icon-send.png', import.meta.url).href  // 发送图标
const stopIcon = new URL('../assets/MainConsole/icon-stop.png', import.meta.url).href  // 停止图标

// 历史记录相关图标
const messageIcon = new URL('../assets/MainConsole/icon-message.png', import.meta.url).href

// 特殊消息背景
const summaryBg = new URL('../assets/MainConsole/bg-summary-bubble.png', import.meta.url).href  // 结论消息的背景图片

// Loading 加载图标
const loadingSpinner = new URL('../assets/Common/loading-spinner.gif', import.meta.url).href

// ==================== 历史记录数据 ====================

/** 历史记录列表数据 */
const historyList = ref<HistoryItem[]>([])
/** 当前页码(用于分页加载) */
const currentPage = ref<number>(1)
/** 是否还有更多历史记录 */
const hasMoreHistory = ref<boolean>(false)
/** 历史记录加载状态 */
const isLoadingHistory = ref<boolean>(false)
/** 聊天消息加载状态 */
const isLoadingMessages = ref<boolean>(false)

// ==================== 聊天消息数据 ====================

/**
 * 聊天消息数组 - 核心数据结构
 * 
 * 消息类型:
 * 1. user - 用户消息 { type: 'user', content: string, time: string }
 * 2. ai - AI消息 { type: 'ai', content: string, time: string, isStreaming?: boolean, isSummary?: boolean }
 * 3. thinking - 思考框 { type: 'thinking', status: 'thinking'|'completed', sections: Array, time: string }
 * 
 * thinking.sections结构:
 * {
 *   agentName: string,           // 智能体名称(逐字符填充)
 *   nodes: Array<{               // 思考节点数组
 *     nodeId: string,            // 节点标题(逐字符填充)
 *     content: string,           // 节点内容(逐字符填充)
 *     timestamp: string
 *   }>,
 *   collapsed: boolean,          // 是否折叠
 * }
 */
const chatMessages = ref<Message[]>([])

// ==================== 新格式SSE数据缓冲区 ====================

/**
 * 用于组装SSE数据的缓冲区
 * 支持两种格式：
 * 1. 旧格式：带 is_partial/part_index/total_parts 字段的分段数据
 * 2. 新格式：不带分段字段，动态长度的JSON片段，通过解析成功判断完成
 */
let partialDataBuffer = ''  // 当前正在组装的数据片段
let currentScreen = 'screen1'  // 当前消息的目标屏幕（screen1 或 screen2）
let streamingMode = false   // 是否启用流式渲染模式（大数据量时）
let lastStreamedLength = 0  // 上次已流式发送的内容长度
let ganttDetected = false   // 是否已检测到gantt类型（避免重复发送消息）

// 旧格式兼容字段（仅在检测到旧格式时使用）
let expectedTotalParts = 0  // 预期的总片段数（旧格式）
let receivedParts = 0       // 已接收的片段数（旧格式）
let isLegacyFormat = false  // 是否为旧格式

/**
 * 重置缓冲区
 */
const resetPartialBuffer = () => {
  partialDataBuffer = ''
  currentScreen = 'screen1'
  streamingMode = false
  lastStreamedLength = 0
  ganttDetected = false
  
  // 旧格式兼容字段
  expectedTotalParts = 0
  receivedParts = 0
  isLegacyFormat = false
}

/**
 * 流式提取并显示 screen1 的智能体内容
 * 核心思路：从部分 JSON 字符串中提取 content 字段，实时显示
 */
const tryStreamScreen1Content = () => {
  if (currentScreen !== 'screen1' || globalCurrentThinkingIndex === null) {
    return
  }

  // 至少有5个字符才尝试提取（兼容小数据块）
  if (partialDataBuffer.length < 5) {
    return
  }

  try {
    // 尝试提取智能体名称和content内容
    // 格式: {"智能体名称": {"role": "assistant", "content": "..."}}
    
    // 1. 查找智能体名称（第一个引号中的内容）
    const nameMatch = partialDataBuffer.match(/^{\"([^"]+)\"/)
    if (!nameMatch) return
    
    const agentName = nameMatch[1]
    
    // 2. 查找 content 字段的内容
    const contentMatch = partialDataBuffer.match(/"content":\s*"([^"]*(?:\\.[^"]*)*)/)
    if (!contentMatch) return
    
    let contentValue = contentMatch[1]
    // 解码转义字符
    contentValue = contentValue
      .replace(/\\n/g, '\n')
      .replace(/\\r/g, '\r')
      .replace(/\\t/g, '\t')
      .replace(/\\"/g, '"')
      .replace(/\\\\/g, '\\')
    
    if (contentValue.length === 0) return
    
    // 3. 检查是否需要创建或更新section
    const thinkingMsg = chatMessages.value[globalCurrentThinkingIndex]
    if (!thinkingMsg || thinkingMsg.type !== 'thinking') return
    
    let sectionIndex = thinkingMsg.sections.findIndex((s: any) => s.agentName === agentName)
    let section = sectionIndex !== -1 ? thinkingMsg.sections[sectionIndex] : null
    
    if (!section) {
      // 创建新section
      section = reactive({
        agentName: agentName,
        content: contentValue,
        collapsed: false,
      })
      thinkingMsg.sections.push(section)
      sectionIndex = thinkingMsg.sections.length - 1 // 获取新创建的索引
      console.log('[Screen1流式] 创建section:', agentName, '初始内容长度:', contentValue.length)
      nextTick(() => {
        scrollToBottom()
        scrollIntentToBottom(globalCurrentThinkingIndex!, sectionIndex)
      })
    } else if (section.content !== contentValue) {
      // 更新已有section
      section.content = contentValue
      nextTick(() => {
        scrollToBottom()
        scrollIntentToBottom(globalCurrentThinkingIndex!, sectionIndex)
      })
    }
  } catch (e) {
    // 忽略解析错误，继续等待更多数据
  }
}

/**
 * 流式提取 Markdown 并发送到 Machine
 * 核心思路：从JSON字符串中提取content字段的Markdown内容，实时渲染
 */
const tryStreamScreen2Content = () => {
  if (!streamingMode || currentScreen !== 'screen2') {
    return
  }

  // 优化：根据缓冲区大小动态调整触发阈值
  // 小数据量(<50字符)：每5字符触发一次，兼容小数据块
  // 中等数据量(50-200字符)：每10字符触发一次
  // 大数据量(>=200字符)：每50字符触发一次，避免频繁渲染
  const newContentLength = partialDataBuffer.length - lastStreamedLength
  let threshold = 5
  if (partialDataBuffer.length >= 50 && partialDataBuffer.length < 200) {
    threshold = 10
  } else if (partialDataBuffer.length >= 200) {
    threshold = 50
  }
  if (newContentLength < threshold) {
    return
  }

  try {
    // 第一步：尝试解析部分JSON,提取智能体信息
    // 缓冲区格式: {"layout": "markmap", "思维导图智能体": [{"content": "# Example Task\n## Main 1\n...
    
    // 提取 layout 类型
    const layoutMatch = partialDataBuffer.match(/"layout":\s*"(\w+)"/)
    if (!layoutMatch) return
    const layoutType = layoutMatch[1]
    
    // 提取智能体名称（第二个引号中的内容）
    const agentMatch = partialDataBuffer.match(/"layout":\s*"\w+",\s*"([^"]+)"/)
    if (!agentMatch) return
    const agentName = agentMatch[1]
    
    // 提取 content 字段的内容（支持多种格式）
    let contentStartIndex = partialDataBuffer.indexOf('"content": "')
    if (contentStartIndex === -1) {
      contentStartIndex = partialDataBuffer.indexOf('"content":"')
    }
    if (contentStartIndex === -1) return

    // 从 "content": " 或 "content":" 之后开始提取
    const prefix = partialDataBuffer.includes('"content": "') ? '"content": "' : '"content":"'
    const startIndex = contentStartIndex + prefix.length
    const substring = partialDataBuffer.substring(startIndex)
    
    // 找到content字段的结束位置（非转义的引号）
    let endIndex = -1
    let escapeCount = 0
    for (let i = 0; i < substring.length; i++) {
      if (substring[i] === '\\') {
        escapeCount++
      } else if (substring[i] === '"') {
        // 如果前面有偶数个反斜杠（包括0个），则这是真正的结束引号
        if (escapeCount % 2 === 0) {
          endIndex = i
          break
        }
        escapeCount = 0
      } else {
        escapeCount = 0
      }
    }
    
    // 如果还没找到结束引号，说明content还在传输中，提取当前所有内容
    const rawContent = endIndex === -1 ? substring : substring.substring(0, endIndex)

    // 第二步：解码提取到的内容
    let content = rawContent
      .replace(/\\n/g, '\n')
      .replace(/\\r/g, '\r')
      .replace(/\\t/g, '\t')
      .replace(/\\"/g, '"')
      .replace(/\\\\/g, '\\')

    if (content.length === 0) return

    console.log(`[流式渲染] 提取内容: layout=${layoutType}, agent=${agentName}, 长度=${content.length}`)
    console.log(`[流式渲染] 预览:`, content.substring(0, 100).replace(/\n/g, ' | '))

    // 第三步：根据 layout 类型发送不同的消息（统一发送 streaming_agent_content）
    window.electronAPI?.sendDirectMessage?.('machine', {
      type: 'streaming_agent_content',
      layout: layoutType,
      agentName: agentName,
      content: content,
      totalLength: content.length,
      timestamp: new Date().toISOString()
    })

    // 更新已发送位置
    lastStreamedLength = partialDataBuffer.length
    console.log(`[流式渲染] 已发送，缓冲区进度: ${lastStreamedLength}/${expectedTotalParts * 10} 字符`)
  } catch (error) {
    console.error('[流式渲染] 提取失败:', error)
  }
}


// ==================== 工具函数 ====================

/**
 * 滚动聊天消息到底部
 * 
 * 使用场景:
 * - 新消息添加后
 * - 打字机效果每个字符添加后
 * - 历史记录加载后
 * 
 * 实现: 使用nextTick确保DOM更新后再滚动
 */
const scrollToBottom = (): void => {
  nextTick(() => {
    if (chatMessagesContainer.value) {
      chatMessagesContainer.value.scrollTop = chatMessagesContainer.value.scrollHeight
    }
  })
}

/**
 * 滚动指定意图内容区域到底部
 * 
 * @param messageIndex - 消息索引
 * @param sectionIndex - section索引
 */
const scrollIntentToBottom = (messageIndex: number, sectionIndex: number): void => {
  const key = `${messageIndex}-${sectionIndex}`
  const el = intentContentRefs.value.get(key)
  if (el) {
    el.scrollTop = el.scrollHeight
  }
}

/**
 * 设置意图内容区域的DOM引用
 * 
 * Vue 3的ref回调函数,每次DOM元素渲染时调用
 * 
 * @param el - DOM元素实例
 * @param messageIndex - 消息索引(思考框在chatMessages中的索引)
 * @param sectionIndex - section索引(智能体section在思考框中的索引)
 */
const setIntentContentRef = (el: any, messageIndex: number, sectionIndex: number): void => {
  if (el) {
    const key = `${messageIndex}-${sectionIndex}`
    intentContentRefs.value.set(key, el as HTMLElement)
  }
}

/**
 * 切换意图理解区域的展开/收缩状态
 * 
 * 功能: 点击智能体section的header时折叠/展开内容
 * 
 * @param messageIndex - 消息索引(思考框位置)
 * @param sectionIndex - section索引(智能体位置)
 */
const toggleThinkingCollapse = (messageIndex: number): void => {
  const msg = chatMessages.value[messageIndex]
  if (msg && msg.type === 'thinking') {
    if (msg.collapsed === undefined) {
      msg.collapsed = false
    }
    msg.collapsed = !msg.collapsed
  }
}

// ==================== 会话管理函数 ====================

/**
 * 创建新会话
 * 
 * 功能:
 * 1. 清空当前聊天内容
 * 2. 重置所有状态标志
 * 3. 发送重置消息到Machine组件(清空方案展示)
 * 
 * 注: 实际的API调用已注释,当前为本地模拟
 */
const createNewSession = (): void => {
  // 调用API创建历史记录（后端自动生成会话名称和图标）
  // createHistoryChat().then((res: any) => {
    // 清空当前聊天内容
    chatMessages.value = []
    receivedMessage.value = ''
    lastUpdate.value = ''
    
    // 重置方案展示完成标志
    solutionDisplayCompleted.value = false
    pendingPhase2Setup = null

    // 发送重置消息到Machine组件(通过Electron的IPC通信)
    if (window.electronAPI && window.electronAPI.sendDirectMessage) {
      window.electronAPI.sendDirectMessage('machine', {
        type: 'reset'
      }).catch((error: Error) => {
        console.error('发送重置消息失败:', error)
      })
    }

    // 显示临时提示(2秒后自动消失)
    // const sessionName = res.title || res.name || '新会话'
    // status.value = `已创建新会话: ${sessionName}`
    setTimeout(() => status.value = '', 2000)
  // }).catch((error: any) => {
  //   console.error('创建会话失败:', error)
  //   status.value = '创建会话失败'
  //   setTimeout(() => status.value = '', 2000)
  // })
}

// ==================== 历史记录管理函数 ====================

/**
 * 加载历史记录列表(首次加载)
 * 
 * 功能:
 * - 重置页码为1
 * - 获取第一页历史记录(每页10条)
 * - 设置是否还有更多记录的标志
 * 
 * 调用时机: 点击历史记录按钮时
 */
const loadHistoryList = (): void => {
  currentPage.value = 1
  isLoadingHistory.value = true

  getHistoryChatList({ page: 1, pageSize: 10 }).then((res: any) => {
    // 后端返回的字段名可能是items或list,兼容处理
    historyList.value = res.items || res.list || []
    hasMoreHistory.value = (res.page || 0) < (res.total_pages || 0)
    isLoadingHistory.value = false
  }).catch((error: any) => {
    console.error('加载历史记录失败:', error)
    historyList.value = []
    hasMoreHistory.value = false
    isLoadingHistory.value = false
  })
}

/**
 * 加载更多历史记录(分页加载)
 * 
 * 功能: 滚动到底部时自动加载下一页
 * 
 * 防抖处理:
 * - 正在加载时不重复请求
 * - 没有更多数据时不请求
 */
const loadMoreHistory = (): void => {
  if (isLoadingHistory.value || !hasMoreHistory.value) {
    return
  }

  isLoadingHistory.value = true
  currentPage.value++

  getHistoryChatList({ page: currentPage.value, pageSize: 10 }).then((res: any) => {
    // 追加新数据到现有列表
    const newItems = res.items || res.list || []
    historyList.value = [...historyList.value, ...newItems]
    hasMoreHistory.value = (res.page || 0) < (res.total_pages || 0)
    isLoadingHistory.value = false
  }).catch((error: any) => {
    console.error('加载更多历史记录失败:', error)
    isLoadingHistory.value = false
  })
}

/**
 * 历史记录列表滚动事件处理
 * 
 * 功能: 检测滚动位置,接近底部时触发加载更多
 * 
 * @param event - 滚动事件对象
 */
const handleHistoryScroll = (event: Event): void => {
  const target = event.target as HTMLElement
  const scrollTop = target.scrollTop
  const scrollHeight = target.scrollHeight
  const clientHeight = target.clientHeight

  // 滚动到底部时加载更多（提前50px触发,优化用户体验）
  if (scrollTop + clientHeight >= scrollHeight - 50) {
    loadMoreHistory()
  }
}

/**
 * 显示历史记录弹框
 * 
 * 功能:
 * - 打开弹框
 * - 加载第一页历史记录
 */
const showHistory = (): void => {
  showHistoryDialog.value = true
  loadHistoryList()
}

/**
 * 关闭历史记录弹框
 */
const closeHistoryDialog = (): void => {
  showHistoryDialog.value = false
}

/**
 * 选择并加载历史记录项
 * 
 * 功能:
 * 1. 从后端获取历史会话的详细数据
 * 2. 转换screen1数据为前端消息格式
 * 3. 显示到聊天区域
 * 4. 发送历史数据到Machine组件同步展示
 * 
 * 注: 当前使用硬编码的mock数据,实际应调用API
 * 
 * @param item - 历史记录列表项
 */
const selectHistoryItem = (item: HistoryItem): void => {
  // 重置方案展示完成标志（加载历史记录时重置）
  solutionDisplayCompleted.value = false
  pendingPhase2Setup = null
  
  // 显示 loading 状态
  isLoadingMessages.value = true
  
  // 添加 3 秒延迟
  setTimeout(() => {
    // TODO: 调用真实API获取历史会话详情
    // const itemId = item.session_id || item.id
    // getHistoryChatById(itemId).then((res: any) => {
    //   if (!res) {
    //     status.value = '加载会话失败'
    //     setTimeout(() => status.value = '', 2000)
    //     return
    //   }
    
    // 临时使用硬编码的mock数据在 content 字符串的换行符 \n 后面添加了两个空格： "- ###### 节点1\n  分析用户需求..."满足样式需求
    let res={
    "session_id": "session_5",
    "title": "会话 5 - 电磁学课程表设计",
    "created_at": "2025-11-07T14:30:00",
    "screen1": [
        {
            "role": "user",
            "content": "我需要一个物理课程表，请帮我设计一个"
        },
        {
            "css": "thinkBegin"
        },
        {
            "需求分析智能体": {
                "role": "assistant",
                "content": "- ###### 节点1\n  分析用户需求，理解要创建物理课程表\n- ###### 节点2\n  确认需要提供多个方案让用户选择"
            }
        },
        {
            "方案设计智能体": {
                "role": "assistant",
                "content": "###### 节点1\n- 准备力学、电磁学、热力学三个方案\n###### 节点2\n- 三个方案已设计完成，准备展示给用户"
            }
        },
        {
            "方案智能体": {
                "role": "assistant",
                "content": "分析用户需求，理解要创建物理课程表"
            }
        },
        {
            "css": "thinkOver"
        },
        {
            "role": "assistant",
            "type": "ask",
            "content": "分析用户需求，理解要创建物理课程表"
        },
        {
            "role": "user",
            "content": "选择方案A"
        },
        {
            "role": "assistant",
            "type": "ask",
            "content": "收到，当前选择方案A"
        },
        {
            "css": "thinkBegin"
        },
        {
            "思维导图智能体": {
                "role": "assistant",
                "content": "###### 节点1\n- 已生成三种方案，请问需要选择哪种方案"
            }
        },
        {
            "代码生成智能体": {
                "role": "assistant",
                "content": "###### 节点1\n- 分析选定方案，准备相关计算代码\n###### 节点2\n- 生成Python代码示例，帮助理解物理概念"
            }
        },
        {
            "输出优化智能体": {
                "role": "assistant",
                "content": "###### 节点1\n- 将所有组件整合成完整的课程方案\n###### 节点2\n- 最终整合输出，确保内容完整性和用户体验"
            }
        },
        {
            "css": "thinkOver"
        },
        {
            "role": "assistant",
            "css": "summary",
            "content": "当前已经输出完成"
        }
    ]
}
    
    /**
     * 数据转换和显示
     * 
     * 1. 使用工具函数convertScreen1ToMessages转换数据格式
     *    - 将后端的screen1数组转换为前端的Message[]格式
     *    - 处理thinkBegin/thinkOver边界创建thinking消息
     *    - 处理智能体消息创建section和nodes
     *    - 处理用户/AI消息创建普通消息气泡
     * 
     * 2. 设置isStreaming=false(历史记录已完成,不显示打字效果)
     * 
     * 3. 滚动到底部显示最新内容
     */
    const messages = convertScreen1ToMessages(res.screen1)
    
    // 将历史聊天记录加载到对话区域
    if (messages.length > 0) {
      chatMessages.value = messages.map((msg: any) => {
        // 普通聊天消息: 标记为非流式状态(不显示打字光标)
        if (msg.type === 'user' || msg.type === 'ai') {
          return {
            ...msg,
            isStreaming: false
          }
        }
        // 思考框消息: 默认折叠
        if (msg.type === 'thinking') {
          return {
            ...msg,
            collapsed: true
          }
        }
        return msg
      })
      // 滚动到底部显示最新消息
      scrollToBottom()
    }
    
    /**
     * 同步数据到Machine组件
     * 
     * 通过Electron IPC发送完整的历史记录数据
     * Machine组件会根据数据展示对应的思维导图、方案等
     */
    if (window.electronAPI && window.electronAPI.sendDirectMessage) {
      window.electronAPI.sendDirectMessage('machine', {
        type: 'history',
        historyData: res
      }).catch((error: Error) => {
        console.error('加载历史记录失败:', error)
      })
    }
    
    // 显示加载成功提示
    const sessionName = item.title || item.name || '会话'
    status.value = `已加载会话: ${sessionName}`
    setTimeout(() => status.value = '', 2000)
    
    // 关闭 loading 状态
    isLoadingMessages.value = false
    
    // 关闭历史记录弹框
    closeHistoryDialog()
  }, 3000)
  // }).catch((error: any) => {
  //   console.error('获取历史记录详情失败:', error)
  //   status.value = '加载会话失败'
  //   setTimeout(() => status.value = '', 2000)
  //   isLoadingMessages.value = false
  // })
}

/**
 * 切换静音/播放状态(同时交换窗口位置)
 * 
 * 功能:
 * 1. 切换静音状态
 * 2. 调用Electron API交换MainConsole和Machine两个窗口的显示位置
 * 
 * 使用场景:
 * - 用户点击左侧静音/播放按钮
 * - 实现双屏切换效果
 * 
 * 注: 此功能依赖Electron的窗口管理API
 */
const toggleMute = (): void => {
  isMuted.value = !isMuted.value
  status.value = isMuted.value ? '已静音' : '已开启播放'
  setTimeout(() => status.value = '', 2000)

  // 调用窗口交换功能(Electron IPC)
  if (window.electronAPI && window.electronAPI.swapWindows) {
    window.electronAPI.swapWindows().catch((error: Error) => {
      console.error('窗口交换失败:', error)
    })
  }
}

/**
 * SSE流式会话数据累积结构
 * 
 * 用途: 在SSE流式传输过程中累积接收到的数据
 * 完成后发送给Machine组件进行综合展示
 */
interface StreamingSessionData {
  thinking_messages: Record<string, any[]>  // 主控制屏的思考消息(当前未使用)
  screen2: any[]                            // Machine组件的数据(思维导图、方案等)
}

/**
 * 处理新格式解析后的完整JSON数据
 * 新格式：前端组装完整JSON后，直接创建UI并显示内容（不再逐字符打字）
 * 
 * 新格式特点：
 * - 后端已经把JSON拆分成单字符发送了
 * - 前端组装完成后应该直接显示完整内容
 * - 不需要再次模拟打字机效果
 */
let handleParsedDataFunc: (parsedData: any) => void = () => {}  // 将在sendMessage中赋值

const handleParsedData = (parsedData: any) => {
  handleParsedDataFunc(parsedData)
}

/**
 * 发送用户消息并处理SSE流式响应 - 核心函数
 * 
 * 这是整个聊天功能的核心,负责:
 * 1. 发送用户消息到后端SSE接口
 * 2. 接收后端的流式数据推送
 * 3. 根据不同消息类型创建UI元素
 * 4. 实现逐字符打字机效果
 * 5. 自动滚动保持最新内容可见
 * 
 * 架构特点:
 * - 元数据先行: 先收到meta创建空容器,再收到字符填充内容
 * - 后端控速: 所有速度控制在后端,前端只负责接收和显示
 * - 任务队列: 使用Promise链确保打字任务串行执行
 * - 双重滚动: 同时滚动聊天区域和意图内容区域
 */
const sendMessage = (): void => {
  // 输入验证
  if (!message.value.trim()) {
    status.value = '请输入消息内容'
    setTimeout(() => status.value = '', 2000)
    return
  }

  const userMessage = message.value.trim()
  const currentTime = new Date().toLocaleString('zh-CN')

  // 添加用户消息到聊天记录(立即显示,不需要打字效果)
  chatMessages.value.push({
    type: 'user',
    content: userMessage,
    time: currentTime
  })

  // 滚动到底部显示用户消息
  scrollToBottom()

  // 点击发送按钮 -> 切换到唤醒视频
  clearVideoTimer()
  isWaitingForUserInput = false
  switchVideo(wakeupVideo, false)
  console.log('点击发送按钮,切换到唤醒视频')
  
  // 设置正在回答状态(禁用发送按钮,显示停止图标)
  isAnswering.value = true

  // 清空输入框
  message.value = ''
  
  // 重置方案展示完成标志（每次发送新消息时重置）
  solutionDisplayCompleted.value = false
  pendingPhase2Setup = null

  /**
   * 会话数据累积对象
   * 在SSE流式传输过程中持续累积数据
   * 传输完成后发送给Machine组件
   */
  const sessionData: StreamingSessionData = {
    thinking_messages: {},
    screen2: []
  }
  
  /**
   * SSE流式处理的状态变量
   * 这些变量在整个SSE流式传输过程中维护UI状态
   */
  
  // 当前活跃的思考框索引(用于新格式的数据处理)
  let currentThinkingBoxIndex: number | null = null

  /**
   * 全局打字任务队列
   * 
   * 为什么需要:
   * - SSE数据是异步到达的,可能同时收到多个字符
   * - 必须确保字符按顺序显示,不能乱序
   * - 使用Promise链将所有打字任务串行化
   * 
   * 工作原理:
   * globalTypingPromise初始为resolved的Promise
   * 每次添加新任务时,追加到Promise链尾部
   * 这样保证所有任务依次执行,不会并发
   */
  let globalTypingPromise: Promise<void> = Promise.resolve()

  /**
   * 实时显示辅助函数
   * 
   * 功能: 立即显示完整文本(不逐字符打字)
   * 
   * 使用场景:
   * - 历史记录中的消息(已完成,不需要打字效果)
   * - 结论气泡的显示(从Machine组件通知触发)
   * 
   * 注: 当前主要用于兼容旧逻辑,新的SSE流式输出都是逐字符的
   * 
   * @param setText - 设置文本的回调函数
   * @param fullText - 完整文本内容
   * @returns Promise - 显示完成的Promise
   */
  const realtimeDisplay = (
    setText: (text: string) => void,
    fullText: string
  ): Promise<void> => {
    return new Promise((resolve) => {
      setText(fullText)
      scrollToBottom()
      resolve()
    })
  }

  /**
   * 添加任务到全局打字队列
   * 
   * 功能: 将新的打字任务追加到Promise链尾部
   * 
   * 为什么这样设计:
   * - 保证所有打字任务串行执行
   * - 防止并发导致的显示乱序
   * - 简化异步流程控制
   * 
   * @param task - 异步任务函数
   */
  const addToGlobalQueue = (task: () => Promise<void>) => {
    globalTypingPromise = globalTypingPromise.then(task)
  }
  
  /**
   * 创建AI气泡并显示完整内容
   * 
   * 功能:
   * 1. 创建空的AI消息气泡
   * 2. 立即显示完整内容(不打字)
   * 3. 标记为已完成状态
   * 
   * 使用场景:
   * - Machine组件通知all_agents_completed时显示结论气泡
   * 
   * @param content - 消息内容
   * @param isSummary - 是否为结论消息(显示特殊背景)
   * @param customTime - 自定义时间戳
   * @returns Promise - 显示完成的Promise
   */
  const createAIBubbleWithDisplay = (
    content: string, 
    isSummary: boolean = false,
    customTime?: string
  ): Promise<void> => {
    return new Promise<void>((resolve) => {
      const msgIndex = chatMessages.value.length
      // 创建AI消息气泡
      chatMessages.value.push({
        type: 'ai' as const,
        content: '',
        time: customTime || new Date().toLocaleString('zh-CN'),
        isStreaming: true,
        isSummary: isSummary
      })
      
      // 立即显示完整内容
      realtimeDisplay(
        (text: string) => {
          const msg = chatMessages.value[msgIndex]
          if (msg.type === 'ai' || msg.type === 'user') {
            msg.content = text
          }
        },
        content
      ).then(() => {
        // 标记为已完成(隐藏打字光标)
        const msg = chatMessages.value[msgIndex]
        if (msg.type === 'ai' || msg.type === 'user') {
          msg.isStreaming = false
        }
        resolve()
      })
    })
  }
  
  /**
   * 将函数引用保存到全局变量
   * 
   * 目的: 在onMounted的消息监听器中使用
   * 场景: Machine组件发送all_agents_completed通知时,需要显示结论气泡
   * 
   * 为什么需要全局引用:
   * - sendMessage是在用户发送消息时调用的
   * - onMounted的监听器可能在任何时候触发
   * - 需要在监听器中访问sendMessage作用域内的函数
   * - 通过闭包保存引用实现跨作用域调用
   */
  globalAddToGlobalQueue = addToGlobalQueue
  globalCreateAIBubble = createAIBubbleWithDisplay

  /**
   * 处理新格式解析后的完整JSON数据的实际逻辑
   * 根据数据结构判断类型并创建相应的UI元素
   */
  handleParsedDataFunc = (parsedData: any) => {
    console.log('[新格式-处理解析数据] 开始处理:', parsedData)
    
    // 处理数组情况：后端可能一次发送多个对象
    if (Array.isArray(parsedData)) {
      console.log('[新格式-处理解析数据] 检测到数组，包含', parsedData.length, '个元素，逐个处理')
      parsedData.forEach((item, index) => {
        console.log(`[新格式-处理解析数据] 处理数组元素 [${index}]:`, item)
        handleParsedDataFunc(item)  // 递归处理每个元素
      })
      return
    }
    
    // 1. 检测thinkBegin/thinkOver（根据css字段或webm字段）
    // 优先检测webm字段用于控制视频
    if (parsedData.webm) {
      const webmType = parsedData.webm
      if (webmType === 'thinkBegin') {
        console.log('[新格式] 收到webm:thinkBegin，切换到思考中视频')
        switchVideo(thinkingVideo, true)
      } else if (webmType === 'thinkOver') {
        console.log('[新格式] 收到webm:thinkOver，切换到思考结束视频')
        // 切换到思考结束视频(循环播放10秒)
        switchVideo(thinkingEndVideo, true)
        
        // 启动10秒定时器切换到唤醒视频
        videoStateTimer = setTimeout(() => {
          isWaitingForUserInput = true  // 标记为等待用户输入状态
          switchVideo(wakeupVideo, true) // 唤醒视频也需要循环播放
          console.log('10秒已到,切换到唤醒视频(循环)')
          
          // 唤醒视频循环播放时，手动启动15秒倒计时切换到待机
          // 注意: 之前的逻辑是在ended事件中触发，现在改为直接倒计时
          clearVideoTimer() // 清除当前的10秒定时器引用(虽然已经执行)
          videoStateTimer = setTimeout(() => {
            if (isWaitingForUserInput) {
              switchVideo(standbyVideo, true)
              isWaitingForUserInput = false
              console.log('15秒内未收到用户输入,切换到待机')
            }
          }, 15000)
        }, 10000)
      }
    }

    if (parsedData.css) {
      const cssType = parsedData.css
      if (cssType === 'thinkBegin') {
        console.log('[新格式] 创建思考框')
        // 视频控制完全交由webm字段处理，css字段不再触视视频
        // if (!parsedData.webm) {
        //   switchVideo(thinkingVideo, true)
        //   console.log('SSE数据到达(css:thinkBegin),切换到思考中视频')
        // }
        
        addToGlobalQueue(() => {
          return new Promise<void>((resolve) => {
            currentThinkingBoxIndex = chatMessages.value.length
            globalCurrentThinkingIndex = currentThinkingBoxIndex  // 同步到全局变量
            chatMessages.value.push({
              type: 'thinking',
              status: 'thinking',
              sections: [],
              time: new Date().toLocaleString('zh-CN')
            })
            nextTick(() => {
              scrollToBottom()
              resolve()
            })
          })
        })
        return  // 添加return，避免继续处理
      } else if (cssType === 'thinkOver') {
        console.log('[新格式] 思考框完成')
        // 视频控制完全交由webm字段处理，css字段不再触视视频
        // if (!parsedData.webm) {
        //   // SSE结束 -> 切换到思考结束视频(循环播放10秒)
        //   switchVideo(thinkingEndVideo, true)
        //   console.log('SSE结束,切换到思考结束视频(循环10秒)')
        //   
        //   // 启动10秒定时器切换到唤醒视频
        //   clearVideoTimer()
        //   videoStateTimer = setTimeout(() => {
        //     isWaitingForUserInput = true  // 标记为等待用户输入状态
        //     switchVideo(wakeupVideo, false)
        //     console.log('10秒已到,切换到唤醒视频')
        //   }, 10000)
        // }

        addToGlobalQueue(() => {
          return new Promise<void>((resolve) => {
            if (currentThinkingBoxIndex !== null) {
              const thinkingMsg = chatMessages.value[currentThinkingBoxIndex]
              if (thinkingMsg && thinkingMsg.type === 'thinking') {
                thinkingMsg.status = 'completed'
              }
              currentThinkingBoxIndex = null
              globalCurrentThinkingIndex = null  // 同步到全局变量
            }
            nextTick(() => {
              scrollToBottom()
              resolve()
            })
          })
        })
        return  // 添加return，避免继续处理
      } else if (cssType === 'summary') {
        // 处理summary类型的AI消息
        console.log('[新格式] 创建总结消息（summary）')
        const content = parsedData.content || ''
        addToGlobalQueue(() => createAIBubbleWithDisplay(content, true))
        return
      }
    }
    
    // 2. 检测直接的用户/AI消息：格式为 {role: 'user/assistant', content: '...', type?: 'ask'}
    if (parsedData.role && typeof parsedData.role === 'string') {
      // 2.1 直接的用户消息
      if (parsedData.role === 'user') {
        console.log('[新格式] 创建用户消息（直接格式）:', parsedData.content)
        addToGlobalQueue(() => {
          return new Promise<void>((resolve) => {
            chatMessages.value.push({
              type: 'user',
              content: parsedData.content || '',
              time: new Date().toLocaleString('zh-CN')
            })
            nextTick(() => {
              scrollToBottom()
              resolve()
            })
          })
        })
        return
      }
      
      // 2.2 直接的AI消息（非智能体）
      if (parsedData.role === 'assistant') {
        console.log('[新格式] 创建AI消息（直接格式）')
        const isSummary = parsedData.css === 'summary' || parsedData.type === 'summary'
        const content = parsedData.content || ''
        addToGlobalQueue(() => createAIBubbleWithDisplay(content, isSummary))
        return
      }
    }
    
    // 3. 检测智能体数据：格式为 {"智能体名称": {"role": "assistant", "content": "..."}}
    const keys = Object.keys(parsedData)
    console.log('[新格式-处理解析数据] JSON keys:', keys)
    
    if (keys.length > 0) {
      const firstKey = keys[0]
      const firstValue = parsedData[firstKey]
      console.log('[新格式-处理解析数据] firstKey:', firstKey, 'firstValue类型:', typeof firstValue)
      
      if (typeof firstValue === 'object' && firstValue !== null) {
        console.log('[新格式-处理解析数据] firstValue.role:', firstValue.role)
        
        // 3.1 嵌套的用户消息
        if (firstValue.role === 'user') {
          console.log('[新格式] 创建用户消息（嵌套格式）:', firstValue.content)
          addToGlobalQueue(() => {
            return new Promise<void>((resolve) => {
              chatMessages.value.push({
                type: 'user',
                content: firstValue.content || '',
                time: new Date().toLocaleString('zh-CN')
              })
              nextTick(() => {
                scrollToBottom()
                resolve()
              })
            })
          })
          return
        }
        
        // 3.2 智能体数据（key是智能体名称，value包含role和content）
        // 关键：智能体名称不会是 'role', 'type', 'content', 'css' 等保留字
        if (firstValue.role === 'assistant' && 
            firstKey !== 'role' && 
            firstKey !== 'type' && 
            firstKey !== 'content' && 
            firstKey !== 'css') {
          const agentName = firstKey
          const agentContent = firstValue.content || ''
          console.log('[新格式] 检测到智能体数据！名称:', agentName, '内容长度:', agentContent.length)
          console.log('[新格式] 内容原始字符串:', agentContent)
          console.log('[新格式] 内容中的换行符检查:', agentContent.includes('\n') ? '包含真实换行符' : '不包含真实换行符')
          console.log('[新格式] 内容中的\\n检查:', agentContent.includes('\\n') ? '包含字面\\n' : '不包含字面\\n')
          console.log('[新格式] 当前思考框索引:', currentThinkingBoxIndex)
          
          // 直接显示内容,不使用打字机效果(因为后端已经控制了发送速度)
          addToGlobalQueue(() => {
            return new Promise<void>((resolve) => {
              if (currentThinkingBoxIndex === null) {
                // 容错:自动创建思考框
                console.warn('[新格式] 没有活动的思考框，自动创建')
                currentThinkingBoxIndex = chatMessages.value.length
                chatMessages.value.push({
                  type: 'thinking',
                  status: 'thinking',
                  sections: [],
                  time: new Date().toLocaleString('zh-CN')
                })
              }
              
              const thinkingMsg = chatMessages.value[currentThinkingBoxIndex]
              console.log('[新格式] 思考框对象:', thinkingMsg)
              if (thinkingMsg && thinkingMsg.type === 'thinking') {
                // 检查该智能体的section是否已存在
                let existingSection = thinkingMsg.sections.find((s: any) => s.agentName === agentName)
                
                if (!existingSection) {
                  // 首次创建section,内容为当前接收到的内容
                  console.log('[新格式] 创建新section:', agentName)
                  const newSection = reactive({
                    agentName: agentName,
                    content: agentContent,  // 直接设置当前内容
                    collapsed: false,
                    showIcon: false  // 初始不显示完成图标
                  })
                  thinkingMsg.sections.push(newSection)
                } else {
                  // section已存在,追加新内容(支持流式更新)
                  console.log('[新格式] 更新已有section:', agentName, '追加内容长度:', agentContent.length - existingSection.content.length)
                  existingSection.content = agentContent  // 直接替换为最新内容
                }
                
                console.log('[新格式] section已处理，总数:', thinkingMsg.sections.length)
              } else {
                console.error('[新格式] 思考框对象无效！')
              }
              
              nextTick(() => {
                scrollToBottom()
                resolve()
              })
            })
          })
          return
        }
        
        // 3.3 嵌套的AI消息（非智能体）
        if (firstValue.role === 'assistant') {
          console.log('[新格式] 创建AI消息（嵌套格式，非智能体）')
          const isSummary = firstValue.css === 'summary' || firstValue.type === 'summary'
          const content = firstValue.content || ''
          addToGlobalQueue(() => createAIBubbleWithDisplay(content, isSummary))
          return
        }
      }
    }
    
    console.warn('[新格式-处理解析数据] 未知数据格式:', parsedData)
  }

  /**
   * 处理完整数据（旧格式和新格式通用）
   * 解析缓冲区中的完整JSON并分发到对应的处理函数
   */
  const processCompleteData = () => {
    console.log('[SSE] 数据接收完整，开始解析JSON，缓冲区长度:', partialDataBuffer.length)
    
    // 使用全局变量 currentScreen 进行路由
    const targetScreen = currentScreen
    
    // 使用 Promise 处理 JSON 解析
    Promise.resolve(partialDataBuffer)
      .then((buffer) => JSON.parse(buffer))
      .then((parsedData) => {
        console.log(`[SSE] 解析成功 (target=${targetScreen}):`, parsedData)
        
        // 根据screen字段分发数据
        if (targetScreen === 'screen2') {
          console.log('[SSE] 路由到 handleScreen2Data')
          handleScreen2Data(parsedData)
        } else {
          console.log('[SSE] 路由到 handleParsedData (screen1)')
          handleParsedData(parsedData)
        }
      })
      .catch((parseError) => {
        console.error('[SSE] JSON解析失败:', parseError)
        console.error('[SSE] 失败的数据:', partialDataBuffer)
      })
    
    // 重置缓冲区准备接收下一个消息
    resetPartialBuffer()
  }

  /**
   * 尝试解析并完成（新格式专用）
   * 策略：每次接收新数据后都尝试解析缓冲区，如果成功则认为数据接收完成
   * 优化：在解析前先执行流式渲染，确保大数据量时用户能看到实时更新
   */
  const tryParseAndComplete = () => {
    // 至少需要一定长度才尝试解析（避免过早解析）
    if (partialDataBuffer.length < 10) {
      return
    }
    
    try {
      // 尝试解析缓冲区内容
      const parsedData = JSON.parse(partialDataBuffer)
      
      // 解析成功，说明接收到完整JSON
      console.log('[SSE-新格式] JSON解析成功，数据接收完成，缓冲区长度:', partialDataBuffer.length)
      
      // 【关键优化】在处理完整数据前，先执行最后一次流式渲染
      // 确保所有内容都已经显示给用户
      if (streamingMode && currentScreen === 'screen2') {
        tryStreamScreen2Content()
      } else if (currentScreen === 'screen1') {
        tryStreamScreen1Content()
      }
      
      // 处理完整数据
      const targetScreen = currentScreen
      if (targetScreen === 'screen2') {
        console.log('[SSE-新格式] 路由到 handleScreen2Data')
        handleScreen2Data(parsedData)
      } else {
        console.log('[SSE-新格式] 路由到 handleParsedData (screen1)')
        handleParsedData(parsedData)
      }
      
      // 重置缓冲区准备接收下一个消息
      resetPartialBuffer()
      
    } catch (e) {
      // 解析失败，说明数据还不完整，继续等待
      // 不输出错误日志，因为这是正常情况
    }
  }

  /**
   * 处理screen2数据（发送给Machine组件展示）
   */
  const handleScreen2Data = (parsedData: any) => {
    console.log('[Screen2] 处理screen2数据:', parsedData)
    
    // 数组格式：直接添加到sessionData（用于智能体列表等）
    if (Array.isArray(parsedData)) {
      sessionData.screen2.push(...parsedData)
      
      // 实时发送给Machine组件
      if (window.electronAPI && window.electronAPI.sendDirectMessage) {
        window.electronAPI.sendDirectMessage('machine', {
          type: 'streaming_content',
          screen2: parsedData
        }).catch((error: Error) => {
          console.error('[Screen2] 发送数据失败:', error)
        })
      }
    } 
    // 对象格式：包装成数组后添加（用于智能体数据）
    else if (typeof parsedData === 'object' && parsedData !== null) {
      sessionData.screen2.push(parsedData)
      
      // 实时发送给Machine组件（包装成数组）
      if (window.electronAPI && window.electronAPI.sendDirectMessage) {
        window.electronAPI.sendDirectMessage('machine', {
          type: 'streaming_content',
          screen2: [parsedData]  // 包装成数组
        }).catch((error: Error) => {
          console.error('[Screen2] 发送数据失败:', error)
        })
      }
    } 
    else {
      console.warn('[Screen2] 数据格式错误（既不是数组也不是对象）:', parsedData)
    }
  }

  // 调用SSE接口
  sendSSEChat(
    userMessage,
    // onMessage: 接收消息片段
    (chunk: string) => {
      console.log('[SSE] 收到原始chunk:', chunk)
      
      // 使用 Promise 链处理 JSON 解析
      Promise.resolve(chunk)
        .then((rawChunk) => JSON.parse(rawChunk))
        .then((chunkData: any) => {
          console.log('[SSE] 解析后的chunkData:', chunkData)
          
          // ========== 格式检测与处理 ==========
          // 检测旧格式：包含 is_partial 字段
          if (chunkData.hasOwnProperty('is_partial')) {
            // 标记为旧格式
            if (!isLegacyFormat) {
              isLegacyFormat = true
              console.log('[SSE] 检测到旧格式（带分段字段）')
            }
            // ========== 旧格式处理逻辑 ==========
            const content = chunkData.content || ''
            const isPartial = chunkData.is_partial
            const partIndex = chunkData.part_index
            const totalParts = chunkData.total_parts
            const screen = chunkData.screen || 'screen1'
            
            console.log(`[SSE-旧格式] 接收字符片段 ${partIndex}/${totalParts - 1}, is_partial=${isPartial}, screen=${screen}, 长度=${content.length}`)
            
            // 初始化或验证总片段数
            if (partIndex === 0) {
              resetPartialBuffer()
              isLegacyFormat = true
              expectedTotalParts = totalParts
              currentScreen = screen
              
              // 启用流式渲染模式：所有 screen2 数据都启用
              if (screen === 'screen2') {
                streamingMode = true
                console.log(`[流式渲染] 启用流式模式 (screen=${screen}, total_parts=${totalParts})`)
              }
            }
            
            // 追加字符到缓冲区
            partialDataBuffer += content
            receivedParts = partIndex + 1
            
            // 【关键优化】在缓冲区累积时检测 gantt 类型，立即发送加载开始消息
            if (screen === 'screen2' && partialDataBuffer.length >= 30 && !ganttDetected) {
              if (partialDataBuffer.includes('"layout": "gantt"') || 
                  partialDataBuffer.includes('"layout":"gantt"')) {
                
                // 尝试提取智能体名称（用于提前占位）
                // 匹配模式: "layout": "gantt", "AgentName"
                const nameMatch = partialDataBuffer.match(/"layout":\s*"gantt"\s*,\s*"([^"]+)"/)
                if (nameMatch) {
                  const agentName = nameMatch[1]
                  console.log('[流式渲染] 检测到Gantt类型，提取名称:', agentName)
                  
                  if (window.electronAPI && window.electronAPI.sendDirectMessage) {
                    // 1. 先发送占位消息，让Machine端创建智能体并切换选中状态
                    console.log('[流式渲染] 发送Gantt占位消息')
                    window.electronAPI.sendDirectMessage('machine', {
                      type: 'streaming_agent_content',
                      layout: 'gantt',
                      agentName: agentName,
                      content: '', // 空内容占位
                      totalLength: 0,
                      timestamp: new Date().toISOString()
                    }).catch((error: Error) => {
                      console.error('[流式渲染] 发送占位消息失败:', error)
                    })
                    
                    // 2. 再发送加载遮罩开启消息
                    console.log('[流式渲染] 发送加载开始消息')
                    window.electronAPI.sendDirectMessage('machine', {
                      type: 'gantt_loading_start',
                      timestamp: new Date().toISOString()
                    }).catch((error: Error) => {
                      console.error('[流式渲染] 发送gantt_loading_start消息失败:', error)
                    })
                    
                    ganttDetected = true
                  }
                } else {
                   // 如果还没匹配到名称（可能buffer还不够长），暂时不设置ganttDetected，等待下一次片段
                   // 但为了避免死锁（万一格式不匹配导致永远拿不到名称），可以设置一个长度上限
                   if (partialDataBuffer.length > 200) {
                      // 超过200字符还没匹配到名称，可能是格式不对，强制开启遮罩（兜底）
                      console.warn('[流式渲染] 检测到Gantt但无法提取名称，强制开启遮罩')
                      ganttDetected = true
                      if (window.electronAPI && window.electronAPI.sendDirectMessage) {
                        window.electronAPI.sendDirectMessage('machine', {
                          type: 'gantt_loading_start',
                          timestamp: new Date().toISOString()
                        }).catch((error: Error) => {
                          console.error('[流式渲染] 发送gantt_loading_start消息失败:', error)
                        })
                      }
                   }
                }
              }
            }
            
            // 优化：每接收片段就尝试流式渲染
            if (streamingMode) {
              tryStreamScreen2Content()
            } else if (currentScreen === 'screen1') {
              // screen1 也启用流式显示
              tryStreamScreen1Content()
            }
            
            // 判断是否接收完整（旧格式）
            const isComplete = !isPartial || (partIndex === totalParts - 1)
            
            // 调试信息：显示接收进度
            if (partIndex % 10 === 0 || isComplete) {
              console.log(`[SSE-旧格式] 进度: ${receivedParts}/${expectedTotalParts}, 缓冲区长度: ${partialDataBuffer.length}`)
            }
            
            if (isComplete) {
              console.log('[SSE-旧格式] 数据接收完整，开始解析JSON')
              processCompleteData()
            }
            
            return // 旧格式处理完毕
          }
          
          // ========== 新格式处理：不带分段字段 ==========
          // 新格式直接是JSON片段，需要累积并尝试解析
          console.log('[SSE-新格式] 检测到新格式数据（无分段字段）')
          
          // 首次接收时初始化
          if (partialDataBuffer === '') {
            console.log('[SSE-新格式] 开始接收新消息')
            // 尝试从第一个chunk中提取screen信息
            const chunkStr = JSON.stringify(chunkData)
            if (chunkStr.includes('"screen":"screen2"') || chunkStr.includes('"screen": "screen2"')) {
              currentScreen = 'screen2'
              streamingMode = true
              console.log('[SSE-新格式] 检测到screen2，启用流式渲染')
            } else if (chunkStr.includes('"screen":"screen1"') || chunkStr.includes('"screen": "screen1"')) {
              currentScreen = 'screen1'
              console.log('[SSE-新格式] 检测到screen1')
            }
          }
          
          // 将chunk转回字符串并追加到缓冲区
          // 注意：新格式下，chunkData可能是完整对象或部分字符串
          let contentToAppend = ''
          
          // 特殊处理：如果content是对象且包含智能体数据，提取智能体内容
          if (chunkData.content && typeof chunkData.content === 'object') {
            // 检查是否是特殊标记（css, webm等）
            if (chunkData.content.css || chunkData.content.webm) {
              // 这是控制指令，不是实际内容，跳过
              // 如果是thinkBegin，创建思考框
              if (chunkData.content.css === 'thinkBegin') {
                currentThinkingBoxIndex = chatMessages.value.length
                globalCurrentThinkingIndex = currentThinkingBoxIndex
                chatMessages.value.push({
                  type: 'thinking',
                  status: 'thinking',
                  sections: [],
                  time: new Date().toLocaleString('zh-CN')
                })
                nextTick(() => scrollToBottom())
              }
              
              return // 跳过后续处理
            }
            
            // 否则将content对象转为JSON字符串
            contentToAppend = JSON.stringify(chunkData.content)
          } else if (typeof chunkData.content === 'string') {
            contentToAppend = chunkData.content
          } else if (typeof chunkData === 'string') {
            contentToAppend = chunkData
          } else {
            contentToAppend = JSON.stringify(chunkData)
          }
          
          partialDataBuffer += contentToAppend
          console.log(`[SSE-新格式] 累积数据，新增: ${contentToAppend.length}字符，缓冲区总长度: ${partialDataBuffer.length}`)
          
          // 【关键优化】对于大数据块（>500字符），立即触发流式渲染
          if (contentToAppend.length > 500) {
            if (streamingMode) {
              tryStreamScreen2Content()
            } else if (currentScreen === 'screen1') {
              tryStreamScreen1Content()
            }
          }
          
          // Gantt类型检测（与旧格式相同）
          if (currentScreen === 'screen2' && partialDataBuffer.length >= 30 && !ganttDetected) {
            if (partialDataBuffer.includes('"layout": "gantt"') || 
                partialDataBuffer.includes('"layout":"gantt"')) {
              const nameMatch = partialDataBuffer.match(/"layout":\s*"gantt"\s*,\s*"([^"]+)"/)
              if (nameMatch) {
                const agentName = nameMatch[1]
                console.log('[流式渲染-新格式] 检测到Gantt类型，提取名称:', agentName)
                
                if (window.electronAPI && window.electronAPI.sendDirectMessage) {
                  window.electronAPI.sendDirectMessage('machine', {
                    type: 'streaming_agent_content',
                    layout: 'gantt',
                    agentName: agentName,
                    content: '',
                    totalLength: 0,
                    timestamp: new Date().toISOString()
                  }).catch((error: Error) => {
                    console.error('[流式渲染-新格式] 发送占位消息失败:', error)
                  })
                  
                  window.electronAPI.sendDirectMessage('machine', {
                    type: 'gantt_loading_start',
                    timestamp: new Date().toISOString()
                  }).catch((error: Error) => {
                    console.error('[流式渲染-新格式] 发送加载开始消息失败:', error)
                  })
                  
                  ganttDetected = true
                }
              } else if (partialDataBuffer.length > 200) {
                console.warn('[流式渲染-新格式] 检测到Gantt但无法提取名称，强制开启遮罩')
                ganttDetected = true
                if (window.electronAPI && window.electronAPI.sendDirectMessage) {
                  window.electronAPI.sendDirectMessage('machine', {
                    type: 'gantt_loading_start',
                    timestamp: new Date().toISOString()
                  }).catch((error: Error) => {
                    console.error('[流式渲染-新格式] 发送加载开始消息失败:', error)
                  })
                }
              }
            }
          }
          
          // 流式渲染（与旧格式相同）
          if (streamingMode) {
            tryStreamScreen2Content()
          } else if (currentScreen === 'screen1') {
            tryStreamScreen1Content()
          }
          
          // ========== 新格式完成检测：尝试解析JSON ==========
          // 策略：每次接收后都尝试解析，如果成功则认为接收完成
          tryParseAndComplete()
        })
        .catch((e) => {
          console.error('[SSE] chunk解析失败（可能是非JSON格式）:', e)
          console.error('[SSE] 失败的chunk:', chunk)
          // 对于新格式，chunk可能不是JSON，直接追加原始字符串
          if (!isLegacyFormat) {
            console.log('[SSE-新格式] 尝试直接追加原始chunk')
            partialDataBuffer += chunk
            
            // 尝试流式渲染
            if (streamingMode) {
              tryStreamScreen2Content()
            } else if (currentScreen === 'screen1') {
              tryStreamScreen1Content()
            }
            
            // 尝试解析完成
            tryParseAndComplete()
          }
          console.error('[SSE] 失败的chunk:', chunk)
          // 跳过这个chunk,继续处理后续数据
        })
    },
    // onError: 错误处理
    (error: Error) => {
      console.error('SSE请求失败:', error)
      status.value = 'AI回复失败: ' + error.message
      setTimeout(() => status.value = '', 2000)
      
      // 标记当前思考框结束
      if (currentThinkingBoxIndex !== null) {
        const thinkingMsg = chatMessages.value[currentThinkingBoxIndex]
        if (thinkingMsg && thinkingMsg.type === 'thinking') {
          thinkingMsg.status = 'completed'
        }
      }
      
      // 结束回答状态
      isAnswering.value = false
    },
    // onComplete: 完成回调
    () => {
      console.log('[SSE] 流式传输完成')
      
      // 等待所有打字机效果完成后，确保思考框状态为完成（兜底机制）
      globalTypingPromise.then(() => {
        console.log('[SSE] 所有打字机效果已完成')
        
        // 确保当前思考框状态为完成
        if (currentThinkingBoxIndex !== null) {
          const thinkingMsg = chatMessages.value[currentThinkingBoxIndex]
          if (thinkingMsg && thinkingMsg.type === 'thinking' && thinkingMsg.status !== 'completed') {
            thinkingMsg.status = 'completed'
            console.log('[SSE] 思考框状态设为完成（兜底）')
          }
        }
      })
      
      // 发送完整会话数据给Machine组件（等待Machine组件的all_agents_completed通知后再显示结论）
      console.log('[SSE] 发送完整会话数据到Machine组件')
      console.log('[SSE] 会话数据:', sessionData)
      if (window.electronAPI && window.electronAPI.sendDirectMessage) {
        window.electronAPI.sendDirectMessage('machine', {
          type: 'streaming_complete',
          sessionData: sessionData
        }).catch((error: Error) => {
          console.error('[SSE] 发送会话数据失败:', error)
        })
      }
      
      // SSE流式传输完成，恢复发送按钮状态
      isAnswering.value = false
      
      scrollToBottom()
    }
  )
    .catch((error: Error) => {
      console.error('SSE请求异常:', error)
    })

  // 更新状态
  receivedMessage.value = userMessage
  lastUpdate.value = currentTime
}

// 组件挂载后执行
onMounted(() => {
  // 应用启动时自动创建新会话
  createNewSession()
  selectHistoryItem(1)  // 暂时注释掉，避免自动加载历史记录
  // 监听来自其他窗口的点对点消息
  if (window.electronAPI && window.electronAPI.onDirectMessage) {
    window.electronAPI.onDirectMessage((_event: any, data: any) => {
      // 处理方案展示完成通知
      if (data.type === 'solution_display_completed') {
        solutionDisplayCompleted.value = true
        
        // 执行待处理的第二阶段设置
        if (pendingPhase2Setup) {
          const setupFunc = pendingPhase2Setup
          pendingPhase2Setup = null
          setupFunc()
        }
      } else if (data.type === 'all_agents_completed') {
        // 处理所有智能体完成通知，显示结论气泡
        if (!globalAddToGlobalQueue || !globalCreateAIBubble) {
          console.warn('全局函数未初始化')
          return
        }
        
        // 添加结论气泡到全局队列
        globalAddToGlobalQueue(() => {
          return new Promise<void>((resolve) => {
            const fullContent = '结论已生成，请查看'
            const customTime = data.timestamp || new Date().toLocaleString('zh-CN')
            
            globalCreateAIBubble!(fullContent, true, customTime).then(() => {
              // 完成第二阶段思考框
              if (globalPhase2ThinkingIndex !== null) {
                const thinkingMsg = chatMessages.value[globalPhase2ThinkingIndex]
                if (thinkingMsg && thinkingMsg.type === 'thinking') {
                  thinkingMsg.status = 'completed'
                }
              }
              
              // 注意：isAnswering 已在 SSE onComplete 中设置为 false
              resolve()
            })
          })
        })
      } else if (data.message) {
        // 兼容旧的消息格式
        const receivedMsg = data.message
        receivedMessage.value = receivedMsg
        lastUpdate.value = data.timestamp || new Date().toLocaleString('zh-CN')
      }
    })
  }
})

// 组件卸载
onUnmounted(() => {
  // 清理视频状态定时器
  clearVideoTimer()
})
</script>


